import { NextRequest, NextResponse } from 'next/server' import { createClient } from '@supabase/supabase-js' // Create server-side Supabase client with user session function createServerSupabaseClient(request: Request) { // Get the authorization header from the request const authHeader = request.headers.get('authorization') return createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { auth: { autoRefreshToken: false, persistSession: false }, global: { headers: authHeader ? { Authorization: authHeader } : {} } } ) } export async function POST( request: NextRequest, { params }: { params: { id: string } } ) { try { const supabase = createServerSupabaseClient(request) const bookId = params.id const { message, changes } = await request.json() if (!message || !changes || changes.length === 0) { return NextResponse.json( { error: 'Commit message and changes are required' }, { status: 400 } ) } // Get current user const { data: { user }, error: authError } = await supabase.auth.getUser() if (authError || !user) { return NextResponse.json( { error: 'Authentication required' }, { status: 401 } ) } // Get GitHub integration const { data: profile } = await supabase .from('profiles') .select('github_integrations') .eq('id', user.id) .single() const integration = profile?.github_integrations?.[bookId] if (!integration) { return NextResponse.json( { error: 'GitHub integration not found' }, { status: 404 } ) } // Get book files to create file content for commit const { data: files } = await supabase .from('file_system_items') .select('*') .eq('book_id', bookId) if (!files) { return NextResponse.json( { error: 'No files found' }, { status: 404 } ) } // Create file contents for GitHub commit const fileContents: { [path: string]: string } = {} // Build file structure const buildFilePath = (fileId: string, allFiles: any[]): string => { const file = allFiles.find(f => f.id === fileId) if (!file) return '' // For files, ensure extension is included let fileName = file.name if (file.type === 'file' && file.file_extension && !fileName.includes('.')) { fileName = `${fileName}.${file.file_extension}` } if (!file.parent_id) return fileName const parentPath = buildFilePath(file.parent_id, allFiles) return parentPath ? `${parentPath}/${fileName}` : fileName } // Add all files to commit files.forEach(file => { if (file.type === 'file') { const filePath = buildFilePath(file.id, files) fileContents[filePath] = file.content || '' } }) // Commit via GitHub API const owner = integration.github_username const repo = integration.repository_name const accessToken = integration.access_token // Get current branch reference const branchResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/vnd.github.v3+json' } }) if (!branchResponse.ok) { throw new Error('Failed to get branch reference') } const branchData = await branchResponse.json() const parentSha = branchData.object.sha // Get current tree const treeResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${parentSha}`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/vnd.github.v3+json' } }) if (!treeResponse.ok) { throw new Error('Failed to get current tree') } // Create blobs and tree entries const treeEntries = await Promise.all( Object.entries(fileContents).map(async ([path, content]) => { const blobResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json' }, body: JSON.stringify({ content: Buffer.from(content).toString('base64'), encoding: 'base64' }) }) if (!blobResponse.ok) { throw new Error(`Failed to create blob for ${path}`) } const blob = await blobResponse.json() return { path, mode: '100644', type: 'blob', sha: blob.sha } }) ) // Create new tree const newTreeResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json' }, body: JSON.stringify({ base_tree: parentSha, tree: treeEntries }) }) if (!newTreeResponse.ok) { throw new Error('Failed to create tree') } const newTree = await newTreeResponse.json() // Create commit const commitResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json' }, body: JSON.stringify({ message, tree: newTree.sha, parents: [parentSha], author: { name: user.user_metadata?.full_name || user.email?.split('@')[0] || 'BookWiz User', email: user.email, date: new Date().toISOString() } }) }) if (!commitResponse.ok) { throw new Error('Failed to create commit') } const commit = await commitResponse.json() // Update branch reference const updateRefResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/vnd.github.v3+json', 'Content-Type': 'application/json' }, body: JSON.stringify({ sha: commit.sha }) }) if (!updateRefResponse.ok) { throw new Error('Failed to update branch reference') } return NextResponse.json({ success: true, commit: { sha: commit.sha, message: commit.message, url: commit.html_url } }) } catch (error) { return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to commit changes' }, { status: 500 } ) } }